﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

namespace eBrain
{
    public partial class Exp_Wisconsin : Experiment
    {
        public enum Rule { Shape, Color, Number, None };
        public Rule ActualRule; //, NextRule;
        public Rule[] RuleArray;
        public int RuleArrayCounter = 0;
        public int RuleArraySize = 6;

        public String ShapeRuleName, ColorRuleName, NumberRuleName;

        public String CorrectAnswer, WrongAnswer, ChangeRuleText;
        public string LastCorrectCardFilename = "", ActCardFilename = "", ActStringID = "";
        public Image ActCardImage; //LastSelectedCardImage; //LastCorrectCard, 
        //public int LastCorrectCardID;
        //public int NumCorrectAnwers;
        public const int MaxCorrectAnswers = 6;
        public const int MaxCorrectRules = 6;
        public bool bAllRulesIdentified = false;
        public bool bChangeRule = false;
        public bool bHideNewCard = false;
        public bool bSimplifiedTest = false;

        public int DirectionFontSize, DirectionLineX, DirectionLineY, DirectionLineW, DirectionLineH;
        //bool RuleSequenceDefined = false;
        public int LastCorrectPlace;    //identificativo di dove mettere la carta

        public Card[] RefCardArray, CorrectCardArray;

        public _MCSTStatus MCSTStatus = _MCSTStatus.NoRule;

        //public bool bChangeRule = false;
        public bool bRuleCorrect = false;
        public int CorrectRulesCounter = 0;
        public int CorrectCardsCounter = 0;

        public enum _MCSTStatus { NoRule, FirstRuleSelected, RuleCorrect, RuleIncorrect, ChangeRule }; //Stage0 = in attesa della selezione; Stage1 = criterio corretto; //Stage2 = criterio sbagliato 

        public int AnswerShowTime, ChangeRuleShowTime;
        public bool ResetCounterOnWrongAnswerReset;

        //public Mutex mtx = new Mutex();


        public Exp_Wisconsin(TestControlWindow _tcw, ExperimentMainForm _expw)//, int _w, int _h)
            : base(_tcw, _expw)//, _w, _h) 
        { }


        public Exp_Wisconsin(ExperimentMainForm _expw, string datapath, string logpath, string sInputMode)
            : base(_expw, datapath, logpath, sInputMode)//, _w, _h) 
        { }

        /// <summary>
        /// 
        /// </summary>
        public override void CustomInit()
        {
            RefCardArray = new Card[5];
            CorrectCardArray = new Card[5];

            //RuleSequenceDefined = false;
            CorrectCardsCounter = 0;
            //LastCorrectCard = new Image();
            LastCorrectPlace = -1;
            ActualRule = Rule.None;
            RuleArray = new Rule[RuleArraySize];


        }



        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmldoc"></param>
        public override void CustomXMLLoad(XmlDocument xmldoc)
        {
            int x, y, w, h, y0, x0;
            String sx, sy, imgfile;
            bool active;

            CorrectAnswer = xmldoc.SelectSingleNode("Experiment/CorrectAnswer").InnerText;
            WrongAnswer = xmldoc.SelectSingleNode("Experiment/WrongAnswer").InnerText;
            ChangeRuleText = xmldoc.SelectSingleNode("Experiment/ChangeRuleText").InnerText;
            ShapeRuleName = xmldoc.SelectSingleNode("Experiment/ShapeRuleName").InnerText;
            ColorRuleName = xmldoc.SelectSingleNode("Experiment/ColorRuleName").InnerText;
            NumberRuleName = xmldoc.SelectSingleNode("Experiment/NumberRuleName").InnerText;

            DirectionFontSize = int.Parse(xmldoc.SelectSingleNode("Experiment/DirectionFontSize").InnerText);
            sx = xmldoc.SelectSingleNode("Experiment/DirectionLineX").InnerText;
            sy = xmldoc.SelectSingleNode("Experiment/DirectionLineY").InnerText;

            DirectionLineX = (ScreenWidth * int.Parse(sx.Replace(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol, ""))) / 100;
            DirectionLineY = (ScreenHeight * int.Parse(sy.Replace(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol, ""))) / 100;
            DirectionLineW = int.Parse(xmldoc.SelectSingleNode("Experiment/DirectionLineW").InnerText);
            DirectionLineH = int.Parse(xmldoc.SelectSingleNode("Experiment/DirectionLineH").InnerText);

            AnswerShowTime = int.Parse(xmldoc.SelectSingleNode("Experiment/AnswerShowTime").InnerText);
            ChangeRuleShowTime = int.Parse(xmldoc.SelectSingleNode("Experiment/ChangeRuleShowTime").InnerText);

            ResetCounterOnWrongAnswerReset = bool.Parse(xmldoc.SelectSingleNode("Experiment/ResetCounterOnWrongAnswerReset").InnerText);
            //carico la sequenza di carte fisse e calcolo le posizioni delle carte da inserire
            //Sotto le carte fisse. 
            //Ne approfitto anche per inizializzare la ROIList
            lock (myROIList)
                myROIList.myRoiList.Clear();

            for (int k = 0; k < ROIs.Count; k++)
            {
                RefCardArray[k] = new Card();
                CorrectCardArray[k] = new Card();

                //Calcolo i dati geometrici
                RefCardArray[k].w = int.Parse(ROIs.Item(k).SelectSingleNode("w").InnerText.ToString());
                RefCardArray[k].h = int.Parse(ROIs.Item(k).SelectSingleNode("h").InnerText.ToString());

                sx = ROIs.Item(k).SelectSingleNode("x").InnerText.ToString();
                sy = ROIs.Item(k).SelectSingleNode("y").InnerText.ToString();

                x = int.Parse(sx.Replace(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol, ""));
                y = int.Parse(sy.Replace(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol, ""));

                x0 = (x * ScreenWidth) / 100;
                y0 = (y * ScreenHeight) / 100;

                RefCardArray[k].x = x0 - RefCardArray[k].w / 2;
                RefCardArray[k].y = y0 - RefCardArray[k].h / 2;


                if (k > 0)
                {

                    //Inserisco le coordinate delle posizioni delle carte selezionate (k > 0)
                    CorrectCardArray[k].x = RefCardArray[k].x;
                    CorrectCardArray[k].y = RefCardArray[k].y + (RefCardArray[k].h * 7) / 6;//Da verificare

                    imgfile = ROIs.Item(k).SelectSingleNode("img").InnerText.ToString();
                    if (imgfile != "")
                    {
                        RefCardArray[k].filename = imgfile;
                        RefCardArray[k].im = Image.FromFile(sImagePath + imgfile);
                    }
                }

                active = bool.Parse(ROIs.Item(k).SelectSingleNode("active").InnerText.ToString());
                w = int.Parse(ROIs.Item(k).SelectSingleNode("w").InnerText.ToString());
                h = int.Parse(ROIs.Item(k).SelectSingleNode("h").InnerText.ToString());

                string sItem = ROIs.Item(k).SelectSingleNode("item").InnerText;
                string sID = ROIs.Item(k).SelectSingleNode("ID").InnerText;

                lock (myROIList)
                    myROIList.AddElement(RefCardArray[k].x, RefCardArray[k].y, w, h, active, sID, sItem, 0);
            }
        }

        public override void Custom_StartTrials()
        {
            UpdateTrialReferences();
        }


        public override void Custom_DoNextTrial()
        {
            UpdateTrialReferences();
            UpdateEyetrackerReferences();
            base.Custom_DoNextTrial();
        }


        private void UpdateTrialReferences()
        {
            ActStringID = Trials.Item(ActualTrialNumber).SelectSingleNode("ID").InnerText.ToString();
            ActCardFilename = Trials.Item(ActualTrialNumber).SelectSingleNode("Item/img").InnerText.ToString();
            ActCardImage = Image.FromFile(sImagePath + ActCardFilename);

            RefCardArray[0].im = ActCardImage;
            RefCardArray[0].filename = ActCardFilename;
        }


        private void UpdateEyetrackerReferences()
        {
            //<DataViewer> 
            if (bEyelinkConnected)
            {
                for (int k = 0; k < ROIs.Count; k++)
                {
                    ET.SendMessage("!V IAREA RECTANGLE " + (k + 1).ToString() + " " +
                         (RefCardArray[k].x).ToString() + " " +
                         (RefCardArray[k].y).ToString() + " " +
                         (RefCardArray[k].x + RefCardArray[k].w).ToString() + " " +
                         (RefCardArray[k].y + RefCardArray[k].h).ToString() + " " +
                         ActStringID);
                }

                for (int k = 0; k < ROIs.Count; k++)
                    ET.SendMessage("!V IMGLOAD TOP_LEFT " + sImagePath + RefCardArray[k].filename +
                    " " + RefCardArray[k].x.ToString() + " " + RefCardArray[k].y.ToString());


                for (int k = 1; k < ROIs.Count; k++)
                    if (CorrectCardArray[k].filename != "")
                        ET.SendMessage("!V IAREA RECTANGLE " + (k + 5).ToString() + " " +
                            (CorrectCardArray[k].x).ToString() + " " +
                            (CorrectCardArray[k].y).ToString() + " " +
                            (CorrectCardArray[k].x + RefCardArray[k].w).ToString() + " " +
                            (CorrectCardArray[k].y + RefCardArray[k].h).ToString() + " " +
                            ActStringID);


                for (int k = 1; k < ROIs.Count; k++)
                    if (CorrectCardArray[k].filename != "")
                        ET.SendMessage("!V IMGLOAD TOP_LEFT " + sImagePath + CorrectCardArray[k].filename + " " +
                            CorrectCardArray[k].x.ToString() + " " + CorrectCardArray[k].y.ToString());
            }

        }



        /// <summary>
        /// 
        /// </summary>
        public override void UpdateCanvas()
        {

            UpdateCanvasMtx.WaitOne();

            //TODO: evitare di caricare l'immagine dalla canvas di myExpW, ma crearne una seconda e poi sovrascriverla
            //myExpW.LockCanvasUserCopy();
            Graphics CanvasGraphics = Graphics.FromImage(WorkingCanvas);
            CanvasGraphics.Clear(bkgrnd);

            //Le ROI sono costanti, seleziono il Trial
            //UpdateConstantROIList();

            try
            {

                for (int k = 0; k < ROIs.Count; k++)
                {

                    if (k == 0)
                    {
                        if (!bHideNewCard)
                        {
                            CanvasGraphics.DrawImageUnscaled(RefCardArray[k].im, RefCardArray[k].x, RefCardArray[k].y);
                        }

                    }
                    else
                    {
                        //caso delle quattro carte di riferimento (se non sto usando il BCI2000 Speller) 
                        if (ExpInputMode != InputMode.P300Speller)
                            CanvasGraphics.DrawImageUnscaled(RefCardArray[k].im, RefCardArray[k].x, RefCardArray[k].y);

                        //Controllo che ci siano già delle carte correttamente selezionate 
                        if (CorrectCardArray[k].im != null)
                        {
                            CanvasGraphics.DrawImageUnscaled(CorrectCardArray[k].im, CorrectCardArray[k].x, CorrectCardArray[k].y);
                            //<DataViewer> se è connesso l'eyetracker, spedisco il comando per disegnare l'mmagine nel dataviewer
                        }
                    }

                }


                if (MCSTStatus == _MCSTStatus.RuleCorrect)
                {
                    using (Font font1 = new Font("Arial", DirectionFontSize, FontStyle.Regular, GraphicsUnit.Point))
                    {
                        Rectangle rect1 = new Rectangle((ScreenWidth - DirectionLineW) / 2, DirectionLineY - DirectionLineH / 2,
                            DirectionLineW, DirectionLineH);
                        TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter;
                        TextRenderer.DrawText(CanvasGraphics, CorrectAnswer, font1, rect1, frgrnd, flags);
                    }

                }
                else
                {
                    if (MCSTStatus == _MCSTStatus.RuleIncorrect)
                    {
                        using (Font font1 = new Font("Arial", DirectionFontSize, FontStyle.Regular, GraphicsUnit.Point))
                        {
                            Rectangle rect1 = new Rectangle((ScreenWidth - DirectionLineW) / 2, DirectionLineY - DirectionLineH / 2,
                                DirectionLineW, DirectionLineH);
                            TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter;
                            TextRenderer.DrawText(CanvasGraphics, WrongAnswer, font1, rect1, frgrnd, flags);
                        }
                    }
                    else
                    {
                        if (MCSTStatus == _MCSTStatus.ChangeRule)
                        {

                            using (Font font1 = new Font("Arial", DirectionFontSize, FontStyle.Regular, GraphicsUnit.Point))
                            {
                                Rectangle rect1 = new Rectangle((ScreenWidth - DirectionLineW) / 2, DirectionLineY - DirectionLineH / 2,
                                    DirectionLineW, DirectionLineH);
                                TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter;
                                TextRenderer.DrawText(CanvasGraphics, ChangeRuleText, font1, rect1, frgrnd, flags);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Update Canvas Error: " + ex.Message.ToString() + "\r\n" + ex.StackTrace);
            }

            CanvasGraphics.Dispose();
            myExpW.CopyImage(WorkingCanvas);

            UpdateCanvasMtx.ReleaseMutex();
            //myExpW.UnlockCanvasUserCopy();
        }

        /// <summary>
        /// GEstisco la selezione che è stata richiamata dal gestore di eventi
        /// </summary>
        public override void SelectionHandler()
        {
            //lo metto nella lista dei risultati
            //string s = myParam.KeySequence.Substring(key_typed, 1);
            //String s = "";
            //int outcome;
            //String s = myROIList.myRoiList[ROI_Selected].sTag;
            //int val = myROIList.myRoiList[].iValue;
            //int roi_selected = ROI_Selected + 1; //Escamotage per mantenere la seleziona anche quando ROI_Selecteed torna ad essere 0

            lock (myROIList)
            {
                if (ExpInputMode == InputMode.P300Speller)
                {

                    myExpW.Invoke(new EventHandler(delegate
                    {
                        min_ROI_ID = 1;
                        max_ROI_ID = 4;

                        //ROI_Selected += 1;

                        myTranspLayer.ManageHighlightSelection(myROIList, ROI_Selected, HighlightSelectionColor, iHighlightSelectionSize);

                        //myExpW.Invoke(new EventHandler(delegate {
                        //    myExpW.ManageHighlightSelection(myROIList, ROI_Selected, 1, 4, HighlightSelectionColor, iHighlightSelectionSize);
                        //    myExpW.Refresh(); 

                        //}));


                        //myTranspLayer.ManageHighlightSelection(myROIList, ROI_Selected, 1, 4, HighlightSelectionColor, iHighlightSelectionSize);
                        //myTranspLayer.UpdateP3WinSelectedArea(x0, y0, w, h,

                        //myTranspLayer.UpdateP3WinSelectedArea_Step1(x0, y0, w, h,
                        //    );
                    }));//, 1000);

                }
                else
                {
                    //lo segnalo all'oggetto form, con una persistenza di 1000 ms (TODO: verificare che sia adatta)

                    myExpW.Invoke(new EventHandler(delegate
                    {
                        myExpW.UpdateSelectedArea(myROIList.GD_AreaSelected.x, myROIList.GD_AreaSelected.y,
                            myROIList.GD_AreaSelected.w, myROIList.GD_AreaSelected.h,
                            HighlightSelectionColor, iHighlightSelectionSize);//, 1000);

                        myExpW.Refresh();
                    }));

                    PerformSelection();

                }
            }
        }

        public override void PerformSelection()
        {
            string s = "";
            string note = "";
            int outcome;
            int roi_selected;

            roi_selected = ROI_Selected;

            //inserisco l'immagine della carta selezionata nell'array 
            CorrectCardArray[roi_selected].im = RefCardArray[0].im;
            CorrectCardArray[roi_selected].filename = RefCardArray[0].filename;

            //imposto a false il disegno della carta assegnata (in questo modo rendo maggiormente l'idea dello spostamento della carta
            bHideNewCard = true;

            myLog.Log("Target selected: " + roi_selected.ToString());

            //segnalo l'avvenuta selezione
            if (bUseSounds)
            {
                SPSelection.Play();
            }

            if (CheckActualRule(ActCardFilename, roi_selected))
            {

                //Segnalo che il criterio è corretto
                MCSTStatus = _MCSTStatus.RuleCorrect;

                UpdateCanvas();
                myExpW.Refresh();
                outcome = 1;

                Thread.Sleep(AnswerShowTime);
            }
            else
            {
                outcome = 0;

                //segnalo che il criterio è scorretto
                MCSTStatus = _MCSTStatus.RuleIncorrect;
                UpdateCanvas();
                myExpW.Refresh();

                //myExpW.Invoke(new EventHandler(delegate { myExpW.Refresh(); }));

                Thread.Sleep(AnswerShowTime);
            }

            //sarebbe meglio mettere il riferimento sItem, ma in questo caso è lo stesso
            s = roi_selected.ToString();

            bROISelected = false;

            //resetto la roi selezionata
            ROI_Selected = RoiList.NO_ROI;
            myExpW.TurnOffSelectedArea();
            myExpW.TurnOffInROIArea();

            //in funzione della regola, scrivo la lista dei risultati
            switch (ActualRule)
            {
                case Rule.Color:
                    note = ColorRuleName;
                    break;
                case Rule.Shape:
                    note = ShapeRuleName;
                    break;
                case Rule.Number:
                    note = NumberRuleName;
                    break;
            }

            //Aggiorno la lista dei risultati
            myResultList.Add(new ResultListElement(outcome, s, note));

            if (bAllRulesIdentified)
            {
                //se non ho anche completato le sei regole, allora termino
                if (bEyelinkConnected)
                    ET.SendCommand("TRIAL_RESULT 0");

                //in questo caso devo segnalare che ho terminato il test
                ActualTrialNumber = Trials.Count - 1;

                bHideNewCard = false;
                EndOfTrials = true;
                bAllRulesIdentified = false;

                EndOfTest();
            }

            else
            {
                //Non ho ancora identificato tutte le regole. Procedo 

                //verifico se ci deve essere un cambio di regola
                if (bChangeRule)
                {

                    if (bSimplifiedTest)
                    {
                        //cancello tutte le carte selezionate correttamente, dato che cambia la regola
                        for (int i = 1; i < 5; i++)
                        {
                            CorrectCardArray[i].im = null;
                            CorrectCardArray[i].filename = "";
                        }
                    }

                    bChangeRule = false;

                    //segnalo che devo cambiare criterio
                    MCSTStatus = _MCSTStatus.ChangeRule;
                    UpdateCanvas();
                    myExpW.Refresh();
                    Thread.Sleep(ChangeRuleShowTime);

                }

                //resetto la roi selezionata
                ROI_Selected = RoiList.NO_ROI;
                MCSTStatus = _MCSTStatus.NoRule;
                bHideNewCard = false;


                //<DataViewer>
                if (bEyelinkConnected)
                    ET.SendCommand("TRIAL_RESULT 0");


                //
                if (++ActualTrialNumber >= Trials.Count)
                {
                    ActualTrialNumber = Trials.Count - 1;
                    CorrectRulesCounter = 0;

                    EndOfTrials = true;

                    EndOfTest();
                    //SaveResults();
                    //EndOfTest();
                }
                else
                {
                    //TODO: mettere a posto perchp qui non va benissimo - solo per prova
                    DoNextTrial();
                    //UpdateCanvas();
                    //this.Invalidate();
                }
            }

        }


        public Rule IdentifyRule(String card, String card_ref)
        {
            String[] s1, s2;

            s1 = card.Split('-');
            s2 = card_ref.Split('-');

            if (s1[0] == s2[0])
                return Rule.Shape;

            if (s1[1] == s2[1])
                return Rule.Color;

            if (s1[2] == s2[2])
                return Rule.Number;

            return Rule.None;
        }


        public bool CheckActualRule(String NewCardName, int NewSelection)
        {
            bool to_return = false;
            //Rule ChosenRule = IdentifyRule(NewCardName, RefCardArray[NewSelection].filename);
            ActualRule = IdentifyRule(NewCardName, RefCardArray[NewSelection].filename);


            switch (CorrectRulesCounter)
            {
                //Nel caso non ci sia ancora nessuna regola, prendo il primo criterio come buono
                case 0:
                    //Se non ne ho ancora indovinato una, qualsiasi regola va bene.
                    if (CorrectCardsCounter == 0)
                    {
                        if (ActualRule != Rule.None)
                        {
                            RuleArray[0] = ActualRule;
                            CorrectCardsCounter++;
                            to_return = true;
                        }
                        else
                        {
                            to_return = false;
                        }
                    }
                    else
                    {
                        //sono sempre nella prima serie di carte, ma la macchina ha già selezionato la regola
                        if (ActualRule == RuleArray[0])
                        {
                            CorrectCardsCounter++;
                            to_return = true;
                        }
                        else
                        {
                            to_return = false;
                        }
                    }
                    break;

                //Nel caso abbia già identificato la prima regola, allora cerco la seconda tra le restanti due
                //e preparo la lista dei criteri successiva (il terzo criterio è definito e la ripetizione è definita)
                case 1:

                    //Nel caso non abbia ancora identificato una carta, 
                    //verifico semplicemente che il criterio di selezione non sia quello precedente. 
                    if (CorrectCardsCounter == 0)
                    {
                        //se la regola scelta è differente rispetto alla precedente, 
                        //allora la seleziono
                        if (ActualRule != RuleArray[0])
                        {
                            RuleArray[1] = ActualRule;
                            CorrectCardsCounter++;

                            //definisco la terza regola in modo che sia differente dalle prime due
                            for (int i = 0; i < 3; i++)
                                if ((Rule)i != RuleArray[0] && (Rule)i != RuleArray[1])
                                    RuleArray[2] = (Rule)i;

                            //definisco la seconda ripetizione delle regole
                            for (int i = 0; i < 3; i++)
                                RuleArray[i + 3] = RuleArray[i];


                            to_return = true;
                        }
                        else
                        {
                            to_return = false;
                        }
                    }
                    else
                    {
                        if (ActualRule == RuleArray[1])
                        {
                            to_return = true;
                            CorrectCardsCounter++;
                        }
                        else
                        {
                            to_return = false;
                        }
                    }
                    break;
                case 2:
                case 3:
                case 4:
                case 5:
                    if (ActualRule == RuleArray[CorrectRulesCounter])
                    {
                        to_return = true;
                        CorrectCardsCounter++;
                    }
                    else
                    {
                        to_return = false;
                    }
                    break;
                case 6:
                    //qui non ci dovrei arrivare
                    to_return = false;
                    break;
            }


            //Controllo se ci sono state almeno sei carte riconosciute, nel qual caso cambio metodo e 
            //se ho finito il dopppio giro di metodi, alzo il flag della fine dell'esperimento
            if (CorrectCardsCounter >= MaxCorrectAnswers)
            {
                if (++CorrectRulesCounter >= MaxCorrectRules)
                {
                    bAllRulesIdentified = true;
                    CorrectRulesCounter = 0;
                }
                else
                {
                    bChangeRule = true;
                }
                CorrectCardsCounter = 0;
            }
            return to_return;
        }
    }

    public class Card
    {
        public String filename = "";
        public Image im;
        public int x, y, w, h;
        //public int y_low; //posizione y dove mettere la carta corrispondente 
    }
}
